/* Linux epoll(2) based ae.c module
 *
 * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Redis nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */


#include <sys/epoll.h>

/**
 * 与kqueue中的状态记录一样
 * 记录epoll调度描述符与预期事件集合
 * 与kqueue一致均为：该预期事件集合即最大一次性取出事件触发的可能数量
 */
typedef struct aeApiState {
    int epfd;
    struct epoll_event *events;
} aeApiState;

// 初始化事件循环创建实际调度执行对象
static int aeApiCreate(aeEventLoop *eventLoop) {
    //分配状态记录空间
    aeApiState *state = zmalloc(sizeof(aeApiState));
    //分配判断
    if (!state) return -1;
    //分配预期事件集合
    state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);
    //若分配置失败则释放并返回
    if (!state->events) {
        zfree(state);
        return -1;
    }
    //创建epoll调度描述符，注意参数1024该参数在linux2.6.8之后的版本将被忽略（但必须大于0）
    //其参数含义为在linux内核中进行初始该预期值大小的事件空间
    state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
    //分配判断，若失败则释放并返回
    if (state->epfd == -1) {
        zfree(state->events);
        zfree(state);
        return -1;
    }
    //初始化事件循环实际调度状态信息
    eventLoop->apidata = state;
    return 0;
}
//重置调度预期事件集合
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
    aeApiState *state = eventLoop->apidata;
    //重新分配该预期空间，若在相应内存页能进行扩展则指针地址不变，若不能则重新分配
    state->events = zrealloc(state->events, sizeof(struct epoll_event)*setsize);
    return 0;
}
//释放事件循环中调度状态空间
static void aeApiFree(aeEventLoop *eventLoop) {
    aeApiState *state = eventLoop->apidata;

    //释放调度描述符
    close(state->epfd);
    //释放预期事件集合
    zfree(state->events);
    //释放该调度状态
    zfree(state);
}

//添加相应I/O描述符至监听集合中
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee = {0}; /* avoid valgrind warning */
    /* If the fd was already monitored for some event, we need a MOD
     * operation. Otherwise we need an ADD operation. */
    //若相应I/O描述符事件槽记录的掩码为NONE则进行设置“添加”操作，否则为“修改”操作
    //主要针对的是epoll调度
    int op = eventLoop->events[fd].mask == AE_NONE ?
            EPOLL_CTL_ADD : EPOLL_CTL_MOD;

    //初始化需要监听的事件掩码
    ee.events = 0;
    //合并掩码
    mask |= eventLoop->events[fd].mask; /* Merge old events */
    //若掩码中具有读事件则转换为epoll输入事件
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    //若掩码中具有写事件则转换为epoll输出事件
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    //设置该epoll的监听描述符值
    ee.data.fd = fd;
    //设置该epoll调度控制
    if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;
    return 0;
}

//将相应监听事件从底层移除
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee = {0}; /* avoid valgrind warning */
    //得到去除相关掩码后的结果
    int mask = eventLoop->events[fd].mask & (~delmask);

    ee.events = 0;
    //若此时还具有读写则设置相关底层监听事件
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    //设置监听描述符
    ee.data.fd = fd;
    //判断是否已经正在监听
    if (mask != AE_NONE) {
        //此时采用监听操作
        epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee);
    } else {
        /* Note, Kernel < 2.6.9 requires a non null event pointer even for
         * EPOLL_CTL_DEL. */
        //若未注册执行移除操作
        epoll_ctl(state->epfd,EPOLL_CTL_DEL,fd,&ee);
    }
}
//实际监听调度
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    int retval, numevents = 0;

    //等待相关调度响应，若tvp不为空则会超时返回
    //retval : 响应的计数
    retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
    //若响应大于0则遍历进行事件记录
    if (retval > 0) {
        int j;

        //获取相关事件响应记数
        numevents = retval;
        //遍历
        for (j = 0; j < numevents; j++) {
            int mask = 0;
            //取响应事件
            struct epoll_event *e = state->events+j;
            //若具有输入事件则转换为高层次的读取事件
            if (e->events & EPOLLIN) mask |= AE_READABLE;
            //若具有输出事件则转换为高层次的写入事件
            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
            //若具有错误事件则转换为高层次的写入事件
            if (e->events & EPOLLERR) mask |= AE_WRITABLE;
            //若具有挂起事件则转换为高层次的写入事件
            if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
            //记录被触发的描述符
            eventLoop->fired[j].fd = e->data.fd;
            //同时记录相关操作
            eventLoop->fired[j].mask = mask;
        }
    }
    //返回相应数量
    return numevents;
}
//返回封装底层调度的名称
static char *aeApiName(void) {
    return "epoll";
}
